﻿/**
* CRL
*/
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using CRL.Data;
using CRL.Data.Attribute;
using CRL.Data.LambdaQuery;
using CRL.Data.LambdaQuery.CRLExpression;
using CRL.Mongo.MongoDBEx;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace CRL.Mongo
{
    interface IMongoDBLambdaQuery
    {
        void SetHaving(CRLExpression cRLExpression);
        void SetGroupCount();
    }
    public sealed partial class MongoDBLambdaQuery<T> : LambdaQuery<T>, IMongoDBLambdaQuery
    {
        /// <summary>
        /// lambda查询
        /// </summary>
        /// <param name="_dbContext"></param>
        public MongoDBLambdaQuery(DbContextInner _dbContext)
            : base(_dbContext, true)
        {

        }
        internal CRLExpression __MongoHavingCount;
        public void SetHaving(CRLExpression havingExp)
        {
            __MongoHavingCount = havingExp;
            return;
        }
        internal bool __SetGroupCount;
        public void SetGroupCount()
        {
            __SetGroupCount = true;
        }
        internal BsonDocument __MongoDBFilter = new BsonDocument();
        public override ILambdaQuery<T> Where(Expression<Func<T, bool>> expression)
        {
            if (expression == null)
                return this;
            var crlExpression = FormatExpression(expression.Body);
            var filterData = RouteCRLExpression(crlExpression, false);
            //__MongoDBFilter = __MongoDBFilter & filterData.Filter;
            if (__MongoDBFilter.Any())
            {
                __MongoDBFilter = new BsonDocument("$and", new BsonArray().Add(__MongoDBFilter).Add(filterData.Filter));
            }
            else
            {
                __MongoDBFilter = filterData.Filter;
            }
            return this;
        }
        #region 生成filter
        BsonDocument RenderToBsonDocument(FilterDefinition<T> filter)
        {
            var serializerRegistry = BsonSerializer.SerializerRegistry;
            var documentSerializer = serializerRegistry.GetSerializer<T>();
            return filter.Render(documentSerializer, serializerRegistry);
        }
        internal string getBsonOp(ExpressionType expressionType)
        {
            var op = "";
            switch (expressionType)
            {
                case ExpressionType.Equal:
                    op = "eq";
                    break;
                case ExpressionType.GreaterThan:
                    op = "gt";
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    op = "gte";
                    break;
                case ExpressionType.LessThan:
                    op = "lt";
                    break;
                case ExpressionType.LessThanOrEqual:
                    op = "lte";
                    break;
                case ExpressionType.NotEqual:
                    op = "ne";
                    break;
                case ExpressionType.Add:
                    op = "add";
                    break;
                case ExpressionType.Subtract:
                    op = "subtract";
                    break;
                case ExpressionType.Multiply:
                    op = "multiply";
                    break;
                case ExpressionType.Divide:
                    op = "divide";
                    break;
                case ExpressionType.AndAlso:
                    op = "and";
                    break;
                case ExpressionType.OrElse:
                    op = "or";
                    break;
                default:
                    throw new InvalidCastException($"不支持的运算符 {expressionType}");
            }
            return op;
        }
        internal BsonDocument getMethodCall(MethodCallObj methodInfo, ExpressionType expressionType)
        {
            var builder = Builders<T>.Filter;
            FilterDefinition<T> filter = null;
            //var methodInfo = left.Data as MethodCallObj;
            #region 按方法
            var field = methodInfo.MemberName;
            var args = methodInfo.Args;
            var firstArgs = args.FirstOrDefault();
            switch (methodInfo.MethodName)
            {
                case "Contains":
                    if (firstArgs is IEnumerable && firstArgs.GetType() != typeof(string))//如果是集合,按in
                    {
                        var list1 = convertIEnumerable(firstArgs);
                        filter = builder.In(field, list1);
                        break;
                    }
                    filter = builder.Regex(field, string.Format("{0}", firstArgs));
                    break;
                case "StartsWith":
                    filter = builder.Regex(field, string.Format("^{0}", firstArgs));
                    break;
                case "Like":
                    filter = builder.Regex(field, string.Format("{0}", firstArgs));
                    break;
                case "LikeLeft":
                    filter = builder.Regex(field, string.Format(".+?{0}", firstArgs));
                    break;
                case "LikeRight":
                    filter = builder.Regex(field, string.Format("{0}.+", firstArgs));
                    break;
                case "Between":
                    filter = builder.Gt(field, args[0]) & builder.Lt(field, args[1]);
                    break;
                case "DateDiff":
                    throw new NotSupportedException(methodInfo.MethodName);
                case "IsNullOrEmpty":
                    filter = builder.Eq(field, "") | new BsonDocument(field, BsonNull.Value);
                    break;
                case "In":
                    var list2 = convertIEnumerable(firstArgs);
                    filter = builder.In(field, list2);
                    break;
                case "NotIn":
                    var list3 = convertIEnumerable(firstArgs);
                    filter = builder.Nin(field, list3);
                    break;
                case "Equals":
                    filter = builder.Eq(field, firstArgs);
                    break;
                default:
                    //自定义方法
                    var dic = MethodAnalyze.GetMethods(__DBAdapter);
                    if (!dic.ContainsKey(methodInfo.MethodName))
                    {
                        throw new NotSupportedException(methodInfo.MethodName);//不支持
                    }
                    var newParIndex = 1;
                    dic[methodInfo.MethodName](methodInfo, ref newParIndex, null);
                    return methodInfo.CustomReturn as BsonDocument;
            }
            if (expressionType == ExpressionType.Not)//创建反向操作
            {
                filter = builder.Not(filter);
            }
            #endregion
            return RenderToBsonDocument(filter);
        }
        BsonDocument getFilter(FilterData left, FilterData right, ExpressionType expressionType, bool pipeline)
        {
            var builder = Builders<T>.Filter;
            if (left.Type == CRLExpressionType.Binary)//表示二元运算
            {
                #region 按条件组合
                var op = getBsonOp(expressionType);
                var rightV = right.Type == CRLExpressionType.Value ? BsonValue.Create(right.Data) : right.Filter;
                var bs= new BsonDocument($"${op}", new BsonArray().Add(left.Filter).Add(rightV));
                //如果是聚合,则需要用$expr
                var exps = new ExpressionType[] { ExpressionType.AndAlso, ExpressionType.OrElse};
                if (bs.First().Value is BsonArray && !exps.Contains(expressionType))
                {
                    bs = bs.ToPackage("$expr");
                }
                return bs;
                #endregion
            }
            else if (left.Type == CRLExpressionType.MethodCallArgs)
            {
                var methodInfo = left.Data as MethodCallObj;
                return getMethodCall(methodInfo, expressionType);
            }
            else//按值
            {
                #region 按值
                var op = getBsonOp(expressionType);
                var binaryObj = new binaryObj { op = op, left = left, right = right, pipeline = pipeline };
                if (left.Type == CRLExpressionType.Value)
                {
                    binaryObj.left = right;
                    binaryObj.right = left;
                }
                return binaryObj.ToBson(this);
                #endregion
            }
            //return filter;
        }
        List<object> convertIEnumerable(object args)
        {
            var list = args as IEnumerable;
            var list2 = new List<object>();
            foreach (var s in list)
            {
                list2.Add(s);
            }
            return list2;
        }
        internal FilterData BinaryCRLExpression(CRLExpression left, CRLExpression right, ExpressionType expressionType, bool pipeline)
        {
            var p1 = pipeline;
            if (left.Type == CRLExpressionType.Tree)
            {
                p1 = true;
            }
            var parLeft = RouteCRLExpression(left, p1);
            var parRight = RouteCRLExpression(right, pipeline);
            var filter = getFilter(parLeft, parRight, expressionType, pipeline);
            if (left.Type == CRLExpressionType.Tree && filter.First().Name != "$expr")
            {
                filter = filter.ToPackage("$expr");
            }
            return new FilterData() { Filter = filter, Type = CRLExpressionType.Binary };
        }
        internal FilterData RouteCRLExpression(CRLExpression exp, bool pipeline)
        {
            if (exp.Type == CRLExpressionType.Binary || exp.Type == CRLExpressionType.Tree)//表示二元运算
            {
                return BinaryCRLExpression(exp.Left, exp.Right, exp.ExpType, pipeline);
            }
            else if (exp.Type == CRLExpressionType.MethodCall)
            {
                var methodInfo = exp.Data as MethodCallObj;
                var left = new CRLExpression() { ExpType = methodInfo.ExpressionType, Type = CRLExpressionType.MethodCallArgs, Data = methodInfo };
                var right = new CRLExpression() { ExpType = methodInfo.ExpressionType, Type = CRLExpressionType.MethodCallArgs, Data = methodInfo.Args };
                return BinaryCRLExpression(left, right, methodInfo.ExpressionType, pipeline);
            }
            //按值
            if (exp.Type == CRLExpressionType.Name && exp.Data.ToString().ToLower() == "id")
            {
                exp.Data = "_id";
            }
            return new FilterData() { Data = exp.Data, Type = exp.Type, ModelType = exp.MemberType };
        }

        #endregion
        public override ILambdaQuery<T> OrderBy<TResult>(Expression<Func<T, TResult>> expression, bool desc = true)
        {
            var parameters = expression.Parameters.Select(b => b.Type).ToArray();
            var field = GetSelectField(false, expression.Body, false, parameters).mapping.First();
            __sortFields.Add(new Tuple<FieldMapping, bool>(field, desc));
            return this;
        }

        public override ILambdaQuery<T> OrderByPrimaryKey(bool desc)
        {
            var field = TypeCache.GetTable(typeof(T)).PrimaryKey;
            __sortFields.Add(new Tuple<FieldMapping, bool>(new FieldMapping
            {
                FieldName = field.MapingName,
                ModelType = typeof(T),
                PropertyType = field.PropertyType,
                QueryField = field.MapingName,
                ResultName = field.MemberName
            }, desc));

            return this;
        }

        public override ILambdaQuery<T> Or(Expression<Func<T, bool>> expression)
        {
            var crlExpression = FormatExpression(expression.Body);
            var filterData = RouteCRLExpression(crlExpression, false);
            //__MongoDBFilter = __MongoDBFilter | filterData.Filter;
            __MongoDBFilter = new BsonDocument("$or", new BsonArray().Add(__MongoDBFilter).Add(filterData.Filter));
            return this;
        }
        #region NotSupported

        public override string GetQueryFieldString()
        {
            return "";
        }

        public override void GetQueryConditions(StringBuilder sb, bool withTableName = true)
        {
            //return "";
        }

        public override string GetOrderBy()
        {
            return ""; throw new NotImplementedException();
        }

        #endregion
        public override string GetQuery(bool v)
        {
            var db = DBExtendFactory.CreateDBExtend(__DbContext) as MongoDBExt;
            var bq = db.getBsonQuery(this);
            var json = bq.ToJson();
            try
            {
                json = ConvertJsonString(json);
            }
            catch
            {
            }
            var table = TypeCache.GetTable(typeof(T));
            return $"db.{table.TableName}.aggregate({json})";
        }
        static string ConvertJsonString(string str)
        {
            //格式化json字符串
            JsonSerializer serializer = new JsonSerializer();
            TextReader tr = new StringReader(str);
            JsonTextReader jtr = new JsonTextReader(tr);
            object obj = serializer.Deserialize(jtr);
            if (obj != null)
            {
                StringWriter textWriter = new StringWriter();
                JsonTextWriter jsonWriter = new JsonTextWriter(textWriter)
                {
                    Formatting = Formatting.Indented,
                    Indentation = 4,
                    IndentChar = ' '
                };
                serializer.Serialize(jsonWriter, obj);
                return textWriter.ToString();
            }
            else
            {
                return str;
            }
        }
    }
}
